//
//  tutu_file.c
//  device
//
//  Created by ffly on 2021/3/14.
//

#include <stdio.h>
#include <sys/stat.h>
#include <dirent.h>

#include "esp_log.h"

#include "xlx_file.h"
#include "xlx_define.h"

#define XLX_FILE_NAME_LEN_MAX    256           // 文件名最大长度

static const char* TAG = "File";


const char *base_path = "/xlx_flash";           // 挂载点
static esp_err_t s_vfs_mount_status = ESP_FAIL;      /* -1未挂载或失败，0挂载成功 */

// Handle of the wear levelling library instance
static wl_handle_t s_wl_handle = WL_INVALID_HANDLE;

/*
 * EPS      打开文件
 * @file    文件名
 * @mode    打开方式 (读/写)
 * return   文件句柄
 * 注意     ！！！ 使用短文件名 8.3
*/
static TutuFileHandle tutufile_open(const char * file,const char* mode){  
    TUTU_LOGI(TAG,"%s@line%d",__FUNCTION__,__LINE__);

    TutuFileHandle pFHdler = NULL;
    // pFHdler = (TutuFileHandle)TUTUAlloc(sizeof(TutuFileHandle));
    // pFHdler = (TutuFileHandle)TUTUAlloc(sizeof(TutuFileHandle));
    char f_name[XLX_FILE_NAME_LEN_MAX] = {0};
    TUTUSprintf(f_name, "%s",file);
    // TUTUSprintf(f_name, "%s%s",base_path,file);
    TUTU_LOGI(TAG, "Opening file(%s:%s)",f_name,mode);

    if(ESP_OK != s_vfs_mount_status){
        TUTU_LOGE(TAG, "VFS mount FAIL not mount !!! ");
        return NULL;        
    }

    pFHdler = fopen((const char *)f_name, mode);
    if (pFHdler != NULL) {
        TUTU_LOGI(TAG, "Open file OK (handle@%p)", pFHdler);
    }
    else {
        // 失败
        TUTU_LOGE(TAG, "Failed to open ");
        TUTUFree(pFHdler);
        pFHdler = NULL;
    }
    return pFHdler;
}

/*
 * EPS      写文件(要求先打开)
 * @handle  文件句柄
 * @data    待写内容
 * @len     待写内容的长度
 * return   实际写入的长度
*/
static int tutufile_write(TutuFileHandle handle,const char *data,int len){
    // TUTU_LOGI(TAG,"%s@line%d",__FUNCTION__,__LINE__);
    if(ESP_OK != s_vfs_mount_status){
        TUTU_LOGE(TAG, "VFS mount FAIL not mount !!! ");
        return -1;        
    }

    if (!handle) return -1;
    return fwrite(data,1,len,handle);
}

/*
 * EPS      读文件(要求先打开)
 * @handle  文件句柄
 * @data    待读内容
 * @len     待读内容的长度
 * return   实际读出的长度
*/
static int tutufile_read(TutuFileHandle handle,char *outbuf,int len){
    // TUTU_LOGI(TAG,"%s@line%d",__FUNCTION__,__LINE__);
    if(ESP_OK != s_vfs_mount_status){
        TUTU_LOGE(TAG, "VFS mount FAIL not mount !!! ");
        return -1;        
    }    
    if (!handle) return -1;
    return fread(outbuf, 1, len, handle);
}

/*
 * EPS      关闭文件(要求先打开)
 * @handle  文件句柄
 * return   无
*/
static void tutufile_close(TutuFileHandle handle){
    // TUTU_LOGI(TAG,"%s@line%d",__FUNCTION__,__LINE__);
    if(ESP_OK != s_vfs_mount_status){
        TUTU_LOGE(TAG, "VFS mount FAIL not mount !!! ");
        return;        
    }    
    if (!handle) return;

    fclose(handle);
    // TUTUFree(handle);
}

/*
 * EPS      关闭文件(要求先打开)
 * @handle  文件句柄
 * return   文件内容的长度
*/
static long int tutufile_filesize(TutuFileHandle handle){
    // TUTU_LOGI(TAG,"%s@line%d (handle@%p)",__FUNCTION__,__LINE__, handle);
    if(ESP_OK != s_vfs_mount_status){
        TUTU_LOGE(TAG, "VFS mount FAIL not mount !!! ");
        return -1;        
    }    
    if (!handle) return -1;

    long size = 0;    
    fseek(handle, 0, SEEK_END);
    size = ftell(handle);
    fseek(handle, 0, SEEK_SET);   
    TUTU_LOGI(TAG,"size=%ld, %s@line%d (handle@%p)", size ,__FUNCTION__,__LINE__, handle);
    return size;
}

/*
 * EPS      删除文件
 * @file    文件名
 * return   无
*/
static int tutufile_delfile(const char * file){
    TUTU_LOGI(TAG,"%s@line%d",__FUNCTION__,__LINE__);
    if(ESP_OK != s_vfs_mount_status){
        TUTU_LOGE(TAG, "VFS mount FAIL not mount !!! ");
        return -1;
    }    
    char f_name[XLX_FILE_NAME_LEN_MAX] = {0};
    TUTUSprintf(f_name, "%s/%s",base_path,file);    

    unsigned int ret = remove(f_name);
    TUTU_LOGE(TAG, "delete file %s (code=%d) !!! ", f_name, ret);
    return ret;
}

/*
 * EPS      文件定位到(要求先打开)
 * @handle  文件句柄
 * @pos     定位到新的位置
 * return   文件内容的长度
*/
static int tutufile_seek(TutuFileHandle handle,int pos){
    TUTU_LOGI(TAG,"%s@line%d",__FUNCTION__,__LINE__);
    if(ESP_OK != s_vfs_mount_status){
        TUTU_LOGE(TAG, "VFS mount FAIL not mount !!! ");
        return -1;        
    }
    if (!handle) return -1;    
    return fseek(handle, pos, 0);
}

/*
* 读取文件内容 (自动打开和关闭)
* @file_name 文件名
* @param data 为文件数据
* @param len 输出文件大小
* @return 	1读取成功		0读取失败
*/
char tutufile_read_file(const char *file_name,char *data,unsigned long int *len){
    // TUTU_LOGI(TAG,"%s@line%d",__FUNCTION__,__LINE__);
    if(ESP_OK != s_vfs_mount_status){
        TUTU_LOGE(TAG, "VFS mount FAIL not mount !!! ");
        return -1;        
    }
    char f_name[XLX_FILE_NAME_LEN_MAX] = {0};
    TUTUSprintf(f_name, "%s/%s",base_path,file_name); 
    TutuFileHandle fp = tutufile_open(f_name,"rb");
    char ret = 0;
    if(fp){
        *len = 0;
        long fsize = tutufile_filesize(fp);
        if(fsize > 0){
            tutufile_read(fp,data,(int)fsize);
            *len = (int)fsize;
            ret = 1;
        }
        // TUTU_LOGI(TAG,"%s@line%d(handl=%p)",__FUNCTION__,__LINE__, fp);
        tutufile_close(fp);
    }
    return ret;
}

/*
* 读取文件内容 (自动打开和关闭)
* @file_name 文件名
* @param data 为文件数据
* @param len 输出文件大小, 也是输入文件的最大大小
* @return 	1读取成功		0读取失败
*/
char tutufile_read_file_ex(const char *file_name,char *data,unsigned long int *len){
    // TUTU_LOGI(TAG,"%s@line%d",__FUNCTION__,__LINE__);
    if(ESP_OK != s_vfs_mount_status){
        TUTU_LOGE(TAG, "VFS mount FAIL not mount !!! ");
        return -1;        
    }
    char f_name[XLX_FILE_NAME_LEN_MAX] = {0};
    TUTUSprintf(f_name, "%s/%s",base_path,file_name); 
    TutuFileHandle fp = tutufile_open(f_name,"rb");
    char ret = 0;
    if(fp){
        unsigned long fsize = tutufile_filesize(fp);        
        fsize = (*len > fsize)?fsize:*len;        
        if(fsize > 0){            
            tutufile_read(fp,data,(int)fsize);
            *len = (int)fsize;
            ret = 1;
        }
        else {
            *len = fsize;
        }        
        tutufile_close(fp);
    }
    return ret;
}

/*
* 写文件内容 (自动打开和关闭)
* @file_name 文件名
* @param data 为文件数据
* @param len 输出文件大小
* @return 	1写成功		0写失败
*/
char tutufile_write_file(const char *file_name,char *data,unsigned long int len){    
    TUTU_LOGI(TAG,"%s@line%d, len=%ld",__FUNCTION__,__LINE__, len);
    char ret = 0;

    if(ESP_OK != s_vfs_mount_status){
        TUTU_LOGE(TAG, "VFS mount FAIL not mount !!! ");
        return ret;        
    }
    char f_name[XLX_FILE_NAME_LEN_MAX] = {0};
    TUTUSprintf(f_name, "%s/%s",base_path,file_name); 
    TutuFileHandle fp = tutufile_open(f_name,"wb");
    unsigned long int f_size;
    if(fp){
        f_size = tutufile_write(fp,data,len);
        if(f_size != len){
            TUTU_LOGW(TAG, "Warn: WriteFile!(Expected=%ld, Actual=%ld)", len, f_size);
        }
        else {
            TUTU_LOGW(TAG, "WriteFile: OK(Expected=%ld, Actual=%ld)", len, f_size);
        }
        tutufile_close(fp);
        ret = 1;
    }
    else {
        TUTU_LOGE(TAG, "Failed to WriteFile!");
    }
        
    return ret;
}

/*
* 读取文件大小 (自动打开和关闭)
* @file_name 文件名
* @return 	1读取成功		0读取失败
*/
static long int tutufile_get_file_size(const char *file_name){
    TUTU_LOGI(TAG,"%s@line%d",__FUNCTION__,__LINE__);
    if(ESP_OK != s_vfs_mount_status){
        TUTU_LOGE(TAG, "VFS mount FAIL not mount !!! ");
        return 0;        
    }
    char f_name[XLX_FILE_NAME_LEN_MAX] = {0};
    // TUTUSprintf(f_name, "%s",file_name); 
    TUTUSprintf(f_name, "%s/%s",base_path,file_name); 
    TutuFileHandle fp = tutufile_open(f_name,"rb");
    if(fp){
        long fsize = tutufile_filesize(fp);
        tutufile_close(fp);
        return fsize;
    }
    else 
    {
        TUTU_LOGE(TAG, "Failed to GetFileSize!");
        return 0;
    }
    
}

/*
* 要挂载设备（文件系统），我们需要设备分区的名称，定义 base_path
* 返回：是否挂载成功
*/
int xlx_vfs_mount(void){
    TUTU_LOGI(TAG, "Mounting FAT filesystem");
    // To mount device we need name of device partition, define base_path
    // and allow format partition in case if it is new one and was not formatted before
    const esp_vfs_fat_mount_config_t mount_config = {
            .max_files = 2,                                     // 最大可同时打开的文件数
            .format_if_mount_failed = true,                    // 如果挂载失败，则格式化分区
            .allocation_unit_size = CONFIG_WL_SECTOR_SIZE,
            .use_one_fat = false,      
    };

    s_vfs_mount_status = esp_vfs_fat_spiflash_mount_rw_wl(base_path, "storage", &mount_config, &s_wl_handle);
    TUTU_LOGE(TAG, "Mount (errid=%d, %s)", s_vfs_mount_status, esp_err_to_name(s_vfs_mount_status));
    if (s_vfs_mount_status != ESP_OK) {
        TUTU_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(s_vfs_mount_status));

    }   
    else {
        TUTU_LOGI(TAG, "Mount FATFS OK");
    }
    return s_vfs_mount_status;
}

static void xlx_vfs_unmount(void){
    // Unmount FATFS
    TUTU_LOGI(TAG, "Unmounting FAT filesystem");
    ESP_ERROR_CHECK(esp_vfs_fat_spiflash_unmount_rw_wl(base_path, s_wl_handle));
    s_vfs_mount_status = ESP_FAIL;
}

/*
 * 获取
*/
static void xlx_list_directory(char * out_buff) {
// static void xlx_list_directory(const char *path) {
    if(ESP_OK != s_vfs_mount_status){
        TUTU_LOGE(TAG, "VFS mount FAIL not mount !!! ");
        return;        
    }

    DIR *dir_handle;
    struct dirent *entry;
 
    //dir_handle = opendir(base_path);
    dir_handle = opendir(base_path);

    if (!dir_handle) {
        TUTU_LOGE(TAG, "opendir fail(%s)", base_path);
        perror("opendir");
        return;
    }
    
    strcat(out_buff,"BasePath=\"");
    strcat(out_buff,base_path);
    strcat(out_buff,"\"");

    // 读取文件列表
    while ((entry = readdir(dir_handle))) {       
        char f_name[XLX_FILE_NAME_LEN_MAX+1] = {0};
        char tmp_buff[XLX_FILE_NAME_LEN_MAX+16] = {0};
        TUTUSprintf(f_name, "%s/%s",base_path,(char *)entry->d_name);   
 
        struct stat entry_stat;
        if (stat(f_name, &entry_stat) == -1) {
            // TUTU_LOGE(TAG, "readdir fail(stat,sz=%ld)", entry_stat.st_size);
            perror("stat");
            continue;
        }

        TUTUSprintf(tmp_buff, "%s:%s(%ldbytes)", (S_ISDIR(entry_stat.st_mode)?"dir":"file"), entry->d_name, entry_stat.st_size);
        strcat(out_buff,", \r\n\t\"");
        TUTU_LOGI(TAG, "%s", tmp_buff);
        strcat(out_buff,tmp_buff);
        strcat(out_buff,"\"");
    }
 
    closedir(dir_handle);
}

void TutuFile_Init(TutuFile *myself){
    TUTU_LOGI(TAG, "************* Free heap: %ld\n", esp_get_free_heap_size());
    TUTU_LOGI(TAG, "%s(%d)", __FUNCTION__, __LINE__); 
    // myself->my_open = tutufile_open;
    // myself->my_write = tutufile_write;
    // myself->my_read = tutufile_read;
    // myself->my_close = tutufile_close;
    // myself->my_filesize = tutufile_filesize;
    myself->my_delfile = tutufile_delfile;
    myself->my_seek = tutufile_seek;
    myself->read_file = tutufile_read_file;
    myself->read_file_ex = tutufile_read_file_ex;
    myself->write_file = tutufile_write_file;
    myself->get_file_size = tutufile_get_file_size;

    myself->list_dir = xlx_list_directory;    
    myself->fs_mount = xlx_vfs_mount;
    myself->fs_unmount = xlx_vfs_unmount;

    // EPS32要先安装文件系统
    xlx_vfs_mount();
    TUTU_LOGI(TAG, "************* Free heap: %ld\n", esp_get_free_heap_size());
}